原文链接https://tutorialzine.com/2017/07/javascript-async-await-explained

长期以来,javascript开发者们都必须依赖回调函数来处理异步请求代码。结果就是当我们遇到类似下面的方法时,很多人都会经历的回调地狱。(下面是一段代码,这里省略,可以直接看链接)

幸运的是, 随着ES6的发布,我们有了Promise里面的then(或者称为.then())来处理异步回调,他们提供了很多处理回调的可供选择的方法,而且很多社区快速的运用了他们来替代原来的代码。

现在随着最近Async/Await的加入, Javascript将会越来越容易开发。

什么是 Async/Await?

Async/Await是我们长久一来一直期盼的javascript的一个特性, 它可以让我们在异步函数处理上更加便捷方便而且也易于理解,它是建立在Promise的基础上,而且也是可以兼容所有Promise的常用API。

Async/Await这个名字是来自asyncawait-这两个关键字, 可以帮助我们理清异步代码:

Async - 声明了一个异步函数(async function someName(){...})

  • 在Promise里面自动的转换为一个个常规的函数
  • 在函数内部return的时候就可以调用异步函数解决
  • 这是异步函数就可以使用await了

Awaut - 暂停异步函数的执行(var result = await someAsyncCall();)

  • 运用在Promise回调之前,await会强制代码停止运行直到Promise请求完成并返回结果才会继续代码执行
  • Await只能与Promise一同使用,在回调函数里面是不起作用的
  • Await可以单独用在异步函数里面

下面有一个简单的例子可以帮助我们理解:

 例如我们想从服务器获取一个JSON文件, 我们可以写一个函数使用axios(这里用axios来发送请求)数据库来发送一个HTTP的get请求地址https://tutorialzine.com/misc/files/example.json。发送请求之后必须等待服务器响应,这样的请求就是一个HTTP的异步请求。

 下面我们用两种方法来实现,第一种用Promise, 第二钟使用Async/Await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Promise 方法
function getJSOn() {
//创建一个Promise对象
return new Promise(function (resolve) {
axios.get('https://tutorialzine.com/misc/files/example.json')
.then(function (json) {
//使用resolve返回结果
resolve(json);
});
});
}

// Async/Await 方法

//async关键字会自动创建一个Promise对象并返回
async function getJSONAsync() {
//await关键字帮我们处理了并不需要then方法
let json = await axios.get('https://tutorialzine.com/misc/files/example.json');
//直接返回类似于同步函数
return json;
}

 很明显我们可以看到使用Async/Await版本的代码更简洁也更容易阅读,除了语法不同,其实两个函数是一样的,他们都返回了一个Promise对象和接收了从axios响应过来的JSON数据, 所以异步函数还可以写成这样的:

1
2
3
getJSONAsync().then(function(result){
//DO something with result
})

Async/Await会创建之前那样的promise吗?

 并不全是,当你使用Async/Await时在底层来说还是会使用到Promise,所以如果你对Promise有了一定的理解之后会有很大的帮助,也是我们所推荐的。

 甚至在一些案例当中,如果使用Async/Await不能满足我们要求时,还是会回过头来请求Promise的帮助。其中的一个使用场景就是当我们需要同时等待很多独立的异步回调完成执行。

 下面的例子就是这样:

1
2
3
4
5
6
7
async function getABC() {
let A = await getValueA(); // getValueA 2秒完成
let B = await getValueB(); // getValueB 4秒完成
let C = await getValueC(); // getValueC 3秒完成

return A*B*C;
}

 每一个await的回调都会等待前一个回调返回结果才会执行,所以如果我们一次性请求的话,这整个函数从开始到结束总共需要花费9秒钟(2+4+3)。

 当这三个顺序可变的并不彼此依赖的函数,这并不是最好的解决方法。换句话说,我们不需要知道A是否需要在B的前面获取,而是可以同时获取到三个的值,而尽量用最少的时间等待。

 所以我们就可以使用Promise.all()在同一个时间发生的所有的请求,并且可以确保在往下执行代码之前可以获取到所有的返回值,因为它会让所有的回调函数可以并行发送请求,而不是一个接一个。

1
2
3
4
5
6
async function getABC() {
// 同时请求所有的函数
let results = await Promise.all([ getValueA, getValueB, getValueC ]);

return results.reduce((total,value) => total * value);
}

 这种方法就可以花更少的时间,getValueA 和 getValueC这两个函数在 getValueB完成的时候早就结束了,所以整个函数运行的时间就是最慢的那个请求的时间,有效了减小了总时间。

Async/Await处理错误

 使用Async/Await的另一个好处就是允许我们在try/catch里面获取到任务不可预期的报错,只需要将await回调包起来即可:

1
2
3
4
5
6
7
8
9
async function doSomethingAsync(){
try {
// 这个请求有可能会失败
let result = await someAsyncCall();
}
catch(error) {
// 这里就是获取到的报错信息
}
}

 catch语句会处理从等待异步回调或者任何其他我们写在try语句里面有可能会失败的代码所引发的报错。

 如果情景需要的话,我们也可以从async函数里面抓取到报错,因为所有的async函数都返回一个Promise对象,这样我们就可以很简单的使用.catch()事件来处理我们的回调函数。

1
2
3
4
5
6
7
8
9
10
11
// 不使用try catch语句
async function doSomethingAsync(){
// 异步函数有可能会失败
let result = await someAsyncCall();
return result;
}

// 调用函数的时候来处理报错
doSomethingAsync().
.then(successHandler)
.catch(errorHandler);

 你更喜欢并且坚持使用哪个方法来处理报错是很重要的,同时使用try/catch语句和.catch()会尽可能的定位到问题所在。

浏览器支持

 Async/Await在大部分主流浏览器上都是支持的,但是IE所有的版本(包括IE11)都不兼容在没有使用外部的库之类的话。

 Node开发者们也可以在Node8及以上的版本上体验到async的好处,近两年将会应用于LTS版本。

 如果这篇文章并不能让你理解,这里还有几篇JS的文章likeBabelTypeScript,而且在node.js里面还提供了他们自己的跨平台版本供我们学习 asyncawait

总结

随着Async/Await的出现,javascript语言在代码的可读性以及使用简单的方面上提升了一个很大的跨度,它可以让异步代码的编程变得像常规的同步函数一样方便, 这对于不管是新手还是js的老司机来说都是一件值得感激的事情。